@code-pushup/coverage-plugin 0.45.0 → 0.45.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin.js CHANGED
@@ -3,1059 +3,1060 @@ import chalk5 from "chalk";
3
3
  import { writeFile } from "node:fs/promises";
4
4
  import { dirname } from "node:path";
5
5
 
6
- // packages/utils/src/lib/text-formats/constants.ts
7
- var NEW_LINE = "\n";
8
- var TAB = " ";
6
+ // packages/models/src/lib/implementation/schemas.ts
7
+ import { MATERIAL_ICONS } from "vscode-material-icons";
8
+ import { z } from "zod";
9
9
 
10
- // packages/utils/src/lib/text-formats/html/details.ts
11
- function details(title, content, cfg = { open: false }) {
12
- return `<details${cfg.open ? " open" : ""}>${NEW_LINE}<summary>${title}</summary>${NEW_LINE}${// ⚠️ The blank line is needed to ensure Markdown in content is rendered correctly.
13
- NEW_LINE}${content}${NEW_LINE}${// @TODO in the future we could consider adding it only if the content ends with a code block
14
- // ⚠️ The blank line ensure Markdown in content is rendered correctly.
15
- NEW_LINE}</details>${// ⚠️ The blank line is needed to ensure Markdown after details is rendered correctly.
16
- NEW_LINE}`;
17
- }
10
+ // packages/models/src/lib/implementation/limits.ts
11
+ var MAX_SLUG_LENGTH = 128;
12
+ var MAX_TITLE_LENGTH = 256;
13
+ var MAX_DESCRIPTION_LENGTH = 65536;
14
+ var MAX_ISSUE_MESSAGE_LENGTH = 1024;
18
15
 
19
- // packages/utils/src/lib/text-formats/html/font-style.ts
20
- var boldElement = "b";
21
- function bold(text) {
22
- return `<${boldElement}>${text}</${boldElement}>`;
23
- }
24
- var italicElement = "i";
25
- function italic(text) {
26
- return `<${italicElement}>${text}</${italicElement}>`;
27
- }
28
- var codeElement = "code";
29
- function code(text) {
30
- return `<${codeElement}>${text}</${codeElement}>`;
16
+ // packages/models/src/lib/implementation/utils.ts
17
+ var slugRegex = /^[a-z\d]+(?:-[a-z\d]+)*$/;
18
+ var filenameRegex = /^(?!.*[ \\/:*?"<>|]).+$/;
19
+ function hasDuplicateStrings(strings) {
20
+ const sortedStrings = [...strings].sort();
21
+ const duplStrings = sortedStrings.filter(
22
+ (item, index) => index !== 0 && item === sortedStrings[index - 1]
23
+ );
24
+ return duplStrings.length === 0 ? false : [...new Set(duplStrings)];
31
25
  }
32
-
33
- // packages/utils/src/lib/text-formats/html/link.ts
34
- function link(href, text) {
35
- return `<a href="${href}">${text || href}"</a>`;
26
+ function hasMissingStrings(toCheck, existing) {
27
+ const nonExisting = toCheck.filter((s) => !existing.includes(s));
28
+ return nonExisting.length === 0 ? false : nonExisting;
36
29
  }
37
-
38
- // packages/utils/src/lib/transform.ts
39
- import { platform } from "node:os";
40
- function toUnixNewlines(text) {
41
- return platform() === "win32" ? text.replace(/\r\n/g, "\n") : text;
30
+ function errorItems(items, transform = (itemArr) => itemArr.join(", ")) {
31
+ return transform(items || []);
42
32
  }
43
- function capitalize(text) {
44
- return `${text.charAt(0).toLocaleUpperCase()}${text.slice(
45
- 1
46
- )}`;
33
+ function exists(value) {
34
+ return value != null;
47
35
  }
48
- function toNumberPrecision(value, decimalPlaces) {
49
- return Number(
50
- `${Math.round(
51
- Number.parseFloat(`${value}e${decimalPlaces}`)
52
- )}e-${decimalPlaces}`
36
+ function getMissingRefsForCategories(categories, plugins) {
37
+ if (categories.length === 0) {
38
+ return false;
39
+ }
40
+ const auditRefsFromCategory = categories.flatMap(
41
+ ({ refs }) => refs.filter(({ type }) => type === "audit").map(({ plugin, slug }) => `${plugin}/${slug}`)
42
+ );
43
+ const auditRefsFromPlugins = plugins.flatMap(
44
+ ({ audits, slug: pluginSlug }) => audits.map(({ slug }) => `${pluginSlug}/${slug}`)
45
+ );
46
+ const missingAuditRefs = hasMissingStrings(
47
+ auditRefsFromCategory,
48
+ auditRefsFromPlugins
49
+ );
50
+ const groupRefsFromCategory = categories.flatMap(
51
+ ({ refs }) => refs.filter(({ type }) => type === "group").map(({ plugin, slug }) => `${plugin}#${slug} (group)`)
52
+ );
53
+ const groupRefsFromPlugins = plugins.flatMap(
54
+ ({ groups, slug: pluginSlug }) => Array.isArray(groups) ? groups.map(({ slug }) => `${pluginSlug}#${slug} (group)`) : []
55
+ );
56
+ const missingGroupRefs = hasMissingStrings(
57
+ groupRefsFromCategory,
58
+ groupRefsFromPlugins
53
59
  );
60
+ const missingRefs = [missingAuditRefs, missingGroupRefs].filter((refs) => Array.isArray(refs) && refs.length > 0).flat();
61
+ return missingRefs.length > 0 ? missingRefs : false;
54
62
  }
55
- function toOrdinal(value) {
56
- if (value % 10 === 1 && value % 100 !== 11) {
57
- return `${value}st`;
58
- }
59
- if (value % 10 === 2 && value % 100 !== 12) {
60
- return `${value}nd`;
61
- }
62
- if (value % 10 === 3 && value % 100 !== 13) {
63
- return `${value}rd`;
64
- }
65
- return `${value}th`;
63
+ function missingRefsForCategoriesErrorMsg(categories, plugins) {
64
+ const missingRefs = getMissingRefsForCategories(categories, plugins);
65
+ return `The following category references need to point to an audit or group: ${errorItems(
66
+ missingRefs
67
+ )}`;
66
68
  }
67
69
 
68
- // packages/utils/src/lib/table.ts
69
- function rowToStringArray({ rows, columns = [] }) {
70
- if (Array.isArray(rows.at(0)) && typeof columns.at(0) === "object") {
71
- throw new TypeError(
72
- "Column can`t be object when rows are primitive values"
73
- );
74
- }
75
- return rows.map((row) => {
76
- if (Array.isArray(row)) {
77
- return row.map(String);
78
- }
79
- const objectRow = row;
80
- if (columns.length === 0 || typeof columns.at(0) === "string") {
81
- return Object.values(objectRow).map(String);
82
- }
83
- return columns.map(
84
- ({ key }) => String(objectRow[key])
85
- );
70
+ // packages/models/src/lib/implementation/schemas.ts
71
+ var primitiveValueSchema = z.union([z.string(), z.number()]);
72
+ function executionMetaSchema(options = {
73
+ descriptionDate: "Execution start date and time",
74
+ descriptionDuration: "Execution duration in ms"
75
+ }) {
76
+ return z.object({
77
+ date: z.string({ description: options.descriptionDate }),
78
+ duration: z.number({ description: options.descriptionDuration })
86
79
  });
87
80
  }
88
- function columnsToStringArray({ rows, columns = [] }) {
89
- const firstRow = rows.at(0);
90
- const primitiveRows = Array.isArray(firstRow);
91
- if (typeof columns.at(0) === "string" && !primitiveRows) {
92
- throw new Error("invalid union type. Caught by model parsing.");
93
- }
94
- if (columns.length === 0) {
95
- if (Array.isArray(firstRow)) {
96
- return firstRow.map((_, idx) => String(idx));
97
- }
98
- return Object.keys(firstRow);
99
- }
100
- if (typeof columns.at(0) === "string") {
101
- return columns.map(String);
102
- }
103
- const cols = columns;
104
- return cols.map(({ label, key }) => label ?? capitalize(key));
81
+ var slugSchema = z.string({ description: "Unique ID (human-readable, URL-safe)" }).regex(slugRegex, {
82
+ message: "The slug has to follow the pattern [0-9a-z] followed by multiple optional groups of -[0-9a-z]. e.g. my-slug"
83
+ }).max(MAX_SLUG_LENGTH, {
84
+ message: `slug can be max ${MAX_SLUG_LENGTH} characters long`
85
+ });
86
+ var descriptionSchema = z.string({ description: "Description (markdown)" }).max(MAX_DESCRIPTION_LENGTH).optional();
87
+ var urlSchema = z.string().url();
88
+ var docsUrlSchema = urlSchema.optional().or(z.literal("")).describe("Documentation site");
89
+ var titleSchema = z.string({ description: "Descriptive name" }).max(MAX_TITLE_LENGTH);
90
+ var scoreSchema = z.number({
91
+ description: "Value between 0 and 1"
92
+ }).min(0).max(1);
93
+ function metaSchema(options) {
94
+ const {
95
+ descriptionDescription,
96
+ titleDescription,
97
+ docsUrlDescription,
98
+ description
99
+ } = options ?? {};
100
+ return z.object(
101
+ {
102
+ title: titleDescription ? titleSchema.describe(titleDescription) : titleSchema,
103
+ description: descriptionDescription ? descriptionSchema.describe(descriptionDescription) : descriptionSchema,
104
+ docsUrl: docsUrlDescription ? docsUrlSchema.describe(docsUrlDescription) : docsUrlSchema
105
+ },
106
+ { description }
107
+ );
105
108
  }
106
- function getColumnAlignmentForKeyAndIndex(targetKey, targetIdx, columns = []) {
107
- const column = columns.at(targetIdx) ?? columns.find((col) => col.key === targetKey);
108
- if (typeof column === "string") {
109
- return column;
110
- } else if (typeof column === "object") {
111
- return column.align ?? "center";
112
- } else {
113
- return "center";
114
- }
109
+ var filePathSchema = z.string().trim().min(1, { message: "path is invalid" });
110
+ var fileNameSchema = z.string().trim().regex(filenameRegex, {
111
+ message: `The filename has to be valid`
112
+ }).min(1, { message: "file name is invalid" });
113
+ var positiveIntSchema = z.number().int().positive();
114
+ var nonnegativeIntSchema = z.number().int().nonnegative();
115
+ var nonnegativeNumberSchema = z.number().nonnegative();
116
+ function packageVersionSchema(options) {
117
+ const { versionDescription = "NPM version of the package", required } = options ?? {};
118
+ const packageSchema = z.string({ description: "NPM package name" });
119
+ const versionSchema = z.string({ description: versionDescription });
120
+ return z.object(
121
+ {
122
+ packageName: required ? packageSchema : packageSchema.optional(),
123
+ version: required ? versionSchema : versionSchema.optional()
124
+ },
125
+ { description: "NPM package name and version of a published package" }
126
+ );
115
127
  }
116
- function getColumnAlignmentForIndex(targetIdx, columns = []) {
117
- const column = columns.at(targetIdx);
118
- if (column == null) {
119
- return "center";
120
- } else if (typeof column === "string") {
121
- return column;
122
- } else if (typeof column === "object") {
123
- return column.align ?? "center";
124
- } else {
125
- return "center";
126
- }
128
+ var weightSchema = nonnegativeNumberSchema.describe(
129
+ "Coefficient for the given score (use weight 0 if only for display)"
130
+ );
131
+ function weightedRefSchema(description, slugDescription) {
132
+ return z.object(
133
+ {
134
+ slug: slugSchema.describe(slugDescription),
135
+ weight: weightSchema.describe("Weight used to calculate score")
136
+ },
137
+ { description }
138
+ );
127
139
  }
128
- function getColumnAlignments({
129
- rows,
130
- columns = []
131
- }) {
132
- if (rows.at(0) == null) {
133
- throw new Error("first row can`t be undefined.");
134
- }
135
- if (Array.isArray(rows.at(0))) {
136
- const firstPrimitiveRow = rows.at(0);
137
- return Array.from({ length: firstPrimitiveRow.length }).map(
138
- (_, idx) => getColumnAlignmentForIndex(idx, columns)
139
- );
140
- }
141
- const firstObject = rows.at(0);
142
- return Object.keys(firstObject).map(
143
- (key, idx) => getColumnAlignmentForKeyAndIndex(key, idx, columns)
140
+ function scorableSchema(description, refSchema, duplicateCheckFn, duplicateMessageFn) {
141
+ return z.object(
142
+ {
143
+ slug: slugSchema.describe('Human-readable unique ID, e.g. "performance"'),
144
+ refs: z.array(refSchema).min(1).refine(
145
+ (refs) => !duplicateCheckFn(refs),
146
+ (refs) => ({
147
+ message: duplicateMessageFn(refs)
148
+ })
149
+ ).refine(hasNonZeroWeightedRef, () => ({
150
+ message: "In a category there has to be at least one ref with weight > 0"
151
+ }))
152
+ },
153
+ { description }
144
154
  );
145
155
  }
146
-
147
- // packages/utils/src/lib/text-formats/html/table.ts
148
- function wrap(elem, content) {
149
- return `<${elem}>${content}</${elem}>${NEW_LINE}`;
150
- }
151
- function wrapRow(content) {
152
- const elem = "tr";
153
- return `<${elem}>${NEW_LINE}${content}</${elem}>${NEW_LINE}`;
154
- }
155
- function table(tableData) {
156
- if (tableData.rows.length === 0) {
157
- throw new Error("Data can't be empty");
158
- }
159
- const tableHeaderCols = columnsToStringArray(tableData).map((s) => wrap("th", s)).join("");
160
- const tableHeaderRow = wrapRow(tableHeaderCols);
161
- const tableBody = rowToStringArray(tableData).map((arr) => {
162
- const columns = arr.map((s) => wrap("td", s)).join("");
163
- return wrapRow(columns);
164
- }).join("");
165
- return wrap("table", `${NEW_LINE}${tableHeaderRow}${tableBody}`);
156
+ var materialIconSchema = z.enum(MATERIAL_ICONS, {
157
+ description: "Icon from VSCode Material Icons extension"
158
+ });
159
+ function hasNonZeroWeightedRef(refs) {
160
+ return refs.reduce((acc, { weight }) => weight + acc, 0) !== 0;
166
161
  }
167
162
 
168
- // packages/utils/src/lib/text-formats/md/font-style.ts
169
- var boldWrap = "**";
170
- function bold2(text) {
171
- return `${boldWrap}${text}${boldWrap}`;
172
- }
173
- var italicWrap = "_";
174
- function italic2(text) {
175
- return `${italicWrap}${text}${italicWrap}`;
176
- }
177
- var strikeThroughWrap = "~";
178
- function strikeThrough(text) {
179
- return `${strikeThroughWrap}${text}${strikeThroughWrap}`;
163
+ // packages/models/src/lib/audit.ts
164
+ import { z as z2 } from "zod";
165
+ var auditSchema = z2.object({
166
+ slug: slugSchema.describe("ID (unique within plugin)")
167
+ }).merge(
168
+ metaSchema({
169
+ titleDescription: "Descriptive name",
170
+ descriptionDescription: "Description (markdown)",
171
+ docsUrlDescription: "Link to documentation (rationale)",
172
+ description: "List of scorable metrics for the given plugin"
173
+ })
174
+ );
175
+ var pluginAuditsSchema = z2.array(auditSchema, {
176
+ description: "List of audits maintained in a plugin"
177
+ }).min(1).refine(
178
+ (auditMetadata) => !getDuplicateSlugsInAudits(auditMetadata),
179
+ (auditMetadata) => ({
180
+ message: duplicateSlugsInAuditsErrorMsg(auditMetadata)
181
+ })
182
+ );
183
+ function duplicateSlugsInAuditsErrorMsg(audits) {
184
+ const duplicateRefs = getDuplicateSlugsInAudits(audits);
185
+ return `In plugin audits the following slugs are not unique: ${errorItems(
186
+ duplicateRefs
187
+ )}`;
180
188
  }
181
- var codeWrap = "`";
182
- function code2(text) {
183
- return `${codeWrap}${text}${codeWrap}`;
189
+ function getDuplicateSlugsInAudits(audits) {
190
+ return hasDuplicateStrings(audits.map(({ slug }) => slug));
184
191
  }
185
192
 
186
- // packages/utils/src/lib/text-formats/md/headline.ts
187
- function headline(text, hierarchy = 1) {
188
- return `${"#".repeat(hierarchy)} ${text}${NEW_LINE}`;
189
- }
190
- function h(text, hierarchy = 1) {
191
- return headline(text, hierarchy);
192
- }
193
- function h1(text) {
194
- return headline(text, 1);
195
- }
196
- function h2(text) {
197
- return headline(text, 2);
198
- }
199
- function h3(text) {
200
- return headline(text, 3);
201
- }
202
- function h4(text) {
203
- return headline(text, 4);
204
- }
205
- function h5(text) {
206
- return headline(text, 5);
207
- }
208
- function h6(text) {
209
- return headline(text, 6);
210
- }
193
+ // packages/models/src/lib/audit-output.ts
194
+ import { z as z5 } from "zod";
211
195
 
212
- // packages/utils/src/lib/text-formats/md/image.ts
213
- function image(src, alt) {
214
- return `![${alt}](${src})`;
215
- }
196
+ // packages/models/src/lib/issue.ts
197
+ import { z as z3 } from "zod";
198
+ var sourceFileLocationSchema = z3.object(
199
+ {
200
+ file: filePathSchema.describe("Relative path to source file in Git repo"),
201
+ position: z3.object(
202
+ {
203
+ startLine: positiveIntSchema.describe("Start line"),
204
+ startColumn: positiveIntSchema.describe("Start column").optional(),
205
+ endLine: positiveIntSchema.describe("End line").optional(),
206
+ endColumn: positiveIntSchema.describe("End column").optional()
207
+ },
208
+ { description: "Location in file" }
209
+ ).optional()
210
+ },
211
+ { description: "Source file location" }
212
+ );
213
+ var issueSeveritySchema = z3.enum(["info", "warning", "error"], {
214
+ description: "Severity level"
215
+ });
216
+ var issueSchema = z3.object(
217
+ {
218
+ message: z3.string({ description: "Descriptive error message" }).max(MAX_ISSUE_MESSAGE_LENGTH),
219
+ severity: issueSeveritySchema,
220
+ source: sourceFileLocationSchema.optional()
221
+ },
222
+ { description: "Issue information" }
223
+ );
216
224
 
217
- // packages/utils/src/lib/text-formats/md/link.ts
218
- function link2(href, text) {
219
- return `[${text || href}](${href})`;
220
- }
225
+ // packages/models/src/lib/table.ts
226
+ import { z as z4 } from "zod";
227
+ var tableAlignmentSchema = z4.enum(["left", "center", "right"], {
228
+ description: "Cell alignment"
229
+ });
230
+ var tableColumnObjectSchema = z4.object({
231
+ key: z4.string(),
232
+ label: z4.string().optional(),
233
+ align: tableAlignmentSchema.optional()
234
+ });
235
+ var tableRowObjectSchema = z4.record(primitiveValueSchema, {
236
+ description: "Object row"
237
+ });
238
+ var tableRowPrimitiveSchema = z4.array(primitiveValueSchema, {
239
+ description: "Primitive row"
240
+ });
241
+ var tableSharedSchema = z4.object({
242
+ title: z4.string().optional().describe("Display title for table")
243
+ });
244
+ var tablePrimitiveSchema = tableSharedSchema.merge(
245
+ z4.object(
246
+ {
247
+ columns: z4.array(tableAlignmentSchema).optional(),
248
+ rows: z4.array(tableRowPrimitiveSchema)
249
+ },
250
+ { description: "Table with primitive rows and optional alignment columns" }
251
+ )
252
+ );
253
+ var tableObjectSchema = tableSharedSchema.merge(
254
+ z4.object(
255
+ {
256
+ columns: z4.union([
257
+ z4.array(tableAlignmentSchema),
258
+ z4.array(tableColumnObjectSchema)
259
+ ]).optional(),
260
+ rows: z4.array(tableRowObjectSchema)
261
+ },
262
+ {
263
+ description: "Table with object rows and optional alignment or object columns"
264
+ }
265
+ )
266
+ );
267
+ var tableSchema = (description = "Table information") => z4.union([tablePrimitiveSchema, tableObjectSchema], { description });
221
268
 
222
- // packages/utils/src/lib/text-formats/md/list.ts
223
- function li(text, order = "unordered") {
224
- const style = order === "unordered" ? "-" : "- [ ]";
225
- return `${style} ${text}`;
226
- }
227
- function indentation(text, level = 1) {
228
- return `${TAB.repeat(level)}${text}`;
269
+ // packages/models/src/lib/audit-output.ts
270
+ var auditValueSchema = nonnegativeNumberSchema.describe("Raw numeric value");
271
+ var auditDisplayValueSchema = z5.string({ description: "Formatted value (e.g. '0.9 s', '2.1 MB')" }).optional();
272
+ var auditDetailsSchema = z5.object(
273
+ {
274
+ issues: z5.array(issueSchema, { description: "List of findings" }).optional(),
275
+ table: tableSchema("Table of related findings").optional()
276
+ },
277
+ { description: "Detailed information" }
278
+ );
279
+ var auditOutputSchema = z5.object(
280
+ {
281
+ slug: slugSchema.describe("Reference to audit"),
282
+ displayValue: auditDisplayValueSchema,
283
+ value: auditValueSchema,
284
+ score: scoreSchema,
285
+ details: auditDetailsSchema.optional()
286
+ },
287
+ { description: "Audit information" }
288
+ );
289
+ var auditOutputsSchema = z5.array(auditOutputSchema, {
290
+ description: "List of JSON formatted audit output emitted by the runner process of a plugin"
291
+ }).refine(
292
+ (audits) => !getDuplicateSlugsInAudits2(audits),
293
+ (audits) => ({ message: duplicateSlugsInAuditsErrorMsg2(audits) })
294
+ );
295
+ function duplicateSlugsInAuditsErrorMsg2(audits) {
296
+ const duplicateRefs = getDuplicateSlugsInAudits2(audits);
297
+ return `In plugin audits the slugs are not unique: ${errorItems(
298
+ duplicateRefs
299
+ )}`;
229
300
  }
230
-
231
- // packages/utils/src/lib/text-formats/md/paragraphs.ts
232
- function paragraphs(...sections) {
233
- return sections.filter(Boolean).join(`${NEW_LINE}${NEW_LINE}`);
301
+ function getDuplicateSlugsInAudits2(audits) {
302
+ return hasDuplicateStrings(audits.map(({ slug }) => slug));
234
303
  }
235
304
 
236
- // packages/utils/src/lib/text-formats/md/section.ts
237
- function section(...contents) {
238
- return `${lines(...contents)}${NEW_LINE}`;
305
+ // packages/models/src/lib/category-config.ts
306
+ import { z as z6 } from "zod";
307
+ var categoryRefSchema = weightedRefSchema(
308
+ "Weighted references to audits and/or groups for the category",
309
+ "Slug of an audit or group (depending on `type`)"
310
+ ).merge(
311
+ z6.object({
312
+ type: z6.enum(["audit", "group"], {
313
+ description: "Discriminant for reference kind, affects where `slug` is looked up"
314
+ }),
315
+ plugin: slugSchema.describe(
316
+ "Plugin slug (plugin should contain referenced audit or group)"
317
+ )
318
+ })
319
+ );
320
+ var categoryConfigSchema = scorableSchema(
321
+ "Category with a score calculated from audits and groups from various plugins",
322
+ categoryRefSchema,
323
+ getDuplicateRefsInCategoryMetrics,
324
+ duplicateRefsInCategoryMetricsErrorMsg
325
+ ).merge(
326
+ metaSchema({
327
+ titleDescription: "Category Title",
328
+ docsUrlDescription: "Category docs URL",
329
+ descriptionDescription: "Category description",
330
+ description: "Meta info for category"
331
+ })
332
+ ).merge(
333
+ z6.object({
334
+ isBinary: z6.boolean({
335
+ description: 'Is this a binary category (i.e. only a perfect score considered a "pass")?'
336
+ }).optional()
337
+ })
338
+ );
339
+ function duplicateRefsInCategoryMetricsErrorMsg(metrics) {
340
+ const duplicateRefs = getDuplicateRefsInCategoryMetrics(metrics);
341
+ return `In the categories, the following audit or group refs are duplicates: ${errorItems(
342
+ duplicateRefs
343
+ )}`;
239
344
  }
240
- function lines(...contents) {
241
- return `${contents.filter(Boolean).join(NEW_LINE)}`;
345
+ function getDuplicateRefsInCategoryMetrics(metrics) {
346
+ return hasDuplicateStrings(
347
+ metrics.map(({ slug, type, plugin }) => `${type} :: ${plugin} / ${slug}`)
348
+ );
242
349
  }
243
-
244
- // packages/utils/src/lib/text-formats/md/table.ts
245
- var alignString = /* @__PURE__ */ new Map([
246
- ["left", ":--"],
247
- ["center", ":--:"],
248
- ["right", "--:"]
249
- ]);
250
- function tableRow(rows) {
251
- return `|${rows.join("|")}|`;
350
+ var categoriesSchema = z6.array(categoryConfigSchema, {
351
+ description: "Categorization of individual audits"
352
+ }).refine(
353
+ (categoryCfg) => !getDuplicateSlugCategories(categoryCfg),
354
+ (categoryCfg) => ({
355
+ message: duplicateSlugCategoriesErrorMsg(categoryCfg)
356
+ })
357
+ );
358
+ function duplicateSlugCategoriesErrorMsg(categories) {
359
+ const duplicateStringSlugs = getDuplicateSlugCategories(categories);
360
+ return `In the categories, the following slugs are duplicated: ${errorItems(
361
+ duplicateStringSlugs
362
+ )}`;
252
363
  }
253
- function table2(data) {
254
- if (data.rows.length === 0) {
255
- throw new Error("Data can't be empty");
256
- }
257
- const alignmentRow = getColumnAlignments(data).map(
258
- (s) => alignString.get(s) ?? String(alignString.get("center"))
259
- );
260
- return section(
261
- `${lines(
262
- tableRow(columnsToStringArray(data)),
263
- tableRow(alignmentRow),
264
- ...rowToStringArray(data).map(tableRow)
265
- )}`
266
- );
364
+ function getDuplicateSlugCategories(categories) {
365
+ return hasDuplicateStrings(categories.map(({ slug }) => slug));
267
366
  }
268
367
 
269
- // packages/utils/src/lib/text-formats/index.ts
270
- var md = {
271
- bold: bold2,
272
- italic: italic2,
273
- strikeThrough,
274
- code: code2,
275
- link: link2,
276
- image,
277
- headline,
278
- h,
279
- h1,
280
- h2,
281
- h3,
282
- h4,
283
- h5,
284
- h6,
285
- indentation,
286
- lines,
287
- li,
288
- section,
289
- paragraphs,
290
- table: table2
291
- };
292
- var html = {
293
- bold,
294
- italic,
295
- code,
296
- link,
297
- details,
298
- table
299
- };
368
+ // packages/models/src/lib/commit.ts
369
+ import { z as z7 } from "zod";
370
+ var commitSchema = z7.object(
371
+ {
372
+ hash: z7.string({ description: "Commit SHA (full)" }).regex(
373
+ /^[\da-f]{40}$/,
374
+ "Commit SHA should be a 40-character hexadecimal string"
375
+ ),
376
+ message: z7.string({ description: "Commit message" }),
377
+ date: z7.coerce.date({
378
+ description: "Date and time when commit was authored"
379
+ }),
380
+ author: z7.string({
381
+ description: "Commit author name"
382
+ }).trim()
383
+ },
384
+ { description: "Git commit" }
385
+ );
386
+
387
+ // packages/models/src/lib/core-config.ts
388
+ import { z as z13 } from "zod";
300
389
 
301
- // packages/models/src/lib/implementation/schemas.ts
302
- import { MATERIAL_ICONS } from "vscode-material-icons";
303
- import { z } from "zod";
390
+ // packages/models/src/lib/persist-config.ts
391
+ import { z as z8 } from "zod";
392
+ var formatSchema = z8.enum(["json", "md"]);
393
+ var persistConfigSchema = z8.object({
394
+ outputDir: filePathSchema.describe("Artifacts folder").optional(),
395
+ filename: fileNameSchema.describe("Artifacts file name (without extension)").optional(),
396
+ format: z8.array(formatSchema).optional()
397
+ });
304
398
 
305
- // packages/models/src/lib/implementation/limits.ts
306
- var MAX_SLUG_LENGTH = 128;
307
- var MAX_TITLE_LENGTH = 256;
308
- var MAX_DESCRIPTION_LENGTH = 65536;
309
- var MAX_ISSUE_MESSAGE_LENGTH = 1024;
399
+ // packages/models/src/lib/plugin-config.ts
400
+ import { z as z11 } from "zod";
310
401
 
311
- // packages/models/src/lib/implementation/utils.ts
312
- var slugRegex = /^[a-z\d]+(?:-[a-z\d]+)*$/;
313
- var filenameRegex = /^(?!.*[ \\/:*?"<>|]).+$/;
314
- function hasDuplicateStrings(strings) {
315
- const sortedStrings = [...strings].sort();
316
- const duplStrings = sortedStrings.filter(
317
- (item, index) => index !== 0 && item === sortedStrings[index - 1]
318
- );
319
- return duplStrings.length === 0 ? false : [...new Set(duplStrings)];
402
+ // packages/models/src/lib/group.ts
403
+ import { z as z9 } from "zod";
404
+ var groupRefSchema = weightedRefSchema(
405
+ "Weighted reference to a group",
406
+ "Reference slug to a group within this plugin (e.g. 'max-lines')"
407
+ );
408
+ var groupMetaSchema = metaSchema({
409
+ titleDescription: "Descriptive name for the group",
410
+ descriptionDescription: "Description of the group (markdown)",
411
+ docsUrlDescription: "Group documentation site",
412
+ description: "Group metadata"
413
+ });
414
+ var groupSchema = scorableSchema(
415
+ 'A group aggregates a set of audits into a single score which can be referenced from a category. E.g. the group slug "performance" groups audits and can be referenced in a category',
416
+ groupRefSchema,
417
+ getDuplicateRefsInGroups,
418
+ duplicateRefsInGroupsErrorMsg
419
+ ).merge(groupMetaSchema);
420
+ var groupsSchema = z9.array(groupSchema, {
421
+ description: "List of groups"
422
+ }).optional().refine(
423
+ (groups) => !getDuplicateSlugsInGroups(groups),
424
+ (groups) => ({
425
+ message: duplicateSlugsInGroupsErrorMsg(groups)
426
+ })
427
+ );
428
+ function duplicateRefsInGroupsErrorMsg(groups) {
429
+ const duplicateRefs = getDuplicateRefsInGroups(groups);
430
+ return `In plugin groups the following references are not unique: ${errorItems(
431
+ duplicateRefs
432
+ )}`;
320
433
  }
321
- function hasMissingStrings(toCheck, existing) {
322
- const nonExisting = toCheck.filter((s) => !existing.includes(s));
323
- return nonExisting.length === 0 ? false : nonExisting;
434
+ function getDuplicateRefsInGroups(groups) {
435
+ return hasDuplicateStrings(groups.map(({ slug: ref }) => ref).filter(exists));
324
436
  }
325
- function errorItems(items, transform = (itemArr) => itemArr.join(", ")) {
326
- return transform(items || []);
437
+ function duplicateSlugsInGroupsErrorMsg(groups) {
438
+ const duplicateRefs = getDuplicateSlugsInGroups(groups);
439
+ return `In groups the following slugs are not unique: ${errorItems(
440
+ duplicateRefs
441
+ )}`;
327
442
  }
328
- function exists(value) {
329
- return value != null;
443
+ function getDuplicateSlugsInGroups(groups) {
444
+ return Array.isArray(groups) ? hasDuplicateStrings(groups.map(({ slug }) => slug)) : false;
330
445
  }
331
- function getMissingRefsForCategories(categories, plugins) {
332
- if (categories.length === 0) {
333
- return false;
446
+
447
+ // packages/models/src/lib/runner-config.ts
448
+ import { z as z10 } from "zod";
449
+ var outputTransformSchema = z10.function().args(z10.unknown()).returns(z10.union([auditOutputsSchema, z10.promise(auditOutputsSchema)]));
450
+ var runnerConfigSchema = z10.object(
451
+ {
452
+ command: z10.string({
453
+ description: "Shell command to execute"
454
+ }),
455
+ args: z10.array(z10.string({ description: "Command arguments" })).optional(),
456
+ outputFile: filePathSchema.describe("Output path"),
457
+ outputTransform: outputTransformSchema.optional()
458
+ },
459
+ {
460
+ description: "How to execute runner"
334
461
  }
335
- const auditRefsFromCategory = categories.flatMap(
336
- ({ refs }) => refs.filter(({ type }) => type === "audit").map(({ plugin, slug }) => `${plugin}/${slug}`)
337
- );
338
- const auditRefsFromPlugins = plugins.flatMap(
339
- ({ audits, slug: pluginSlug }) => audits.map(({ slug }) => `${pluginSlug}/${slug}`)
340
- );
341
- const missingAuditRefs = hasMissingStrings(
342
- auditRefsFromCategory,
343
- auditRefsFromPlugins
344
- );
345
- const groupRefsFromCategory = categories.flatMap(
346
- ({ refs }) => refs.filter(({ type }) => type === "group").map(({ plugin, slug }) => `${plugin}#${slug} (group)`)
347
- );
348
- const groupRefsFromPlugins = plugins.flatMap(
349
- ({ groups, slug: pluginSlug }) => Array.isArray(groups) ? groups.map(({ slug }) => `${pluginSlug}#${slug} (group)`) : []
350
- );
351
- const missingGroupRefs = hasMissingStrings(
352
- groupRefsFromCategory,
353
- groupRefsFromPlugins
354
- );
355
- const missingRefs = [missingAuditRefs, missingGroupRefs].filter((refs) => Array.isArray(refs) && refs.length > 0).flat();
356
- return missingRefs.length > 0 ? missingRefs : false;
357
- }
358
- function missingRefsForCategoriesErrorMsg(categories, plugins) {
359
- const missingRefs = getMissingRefsForCategories(categories, plugins);
360
- return `The following category references need to point to an audit or group: ${errorItems(
361
- missingRefs
362
- )}`;
363
- }
462
+ );
463
+ var onProgressSchema = z10.function().args(z10.unknown()).returns(z10.void());
464
+ var runnerFunctionSchema = z10.function().args(onProgressSchema.optional()).returns(z10.union([auditOutputsSchema, z10.promise(auditOutputsSchema)]));
364
465
 
365
- // packages/models/src/lib/implementation/schemas.ts
366
- var primitiveValueSchema = z.union([z.string(), z.number()]);
367
- function executionMetaSchema(options = {
368
- descriptionDate: "Execution start date and time",
369
- descriptionDuration: "Execution duration in ms"
370
- }) {
371
- return z.object({
372
- date: z.string({ description: options.descriptionDate }),
373
- duration: z.number({ description: options.descriptionDuration })
374
- });
375
- }
376
- var slugSchema = z.string({ description: "Unique ID (human-readable, URL-safe)" }).regex(slugRegex, {
377
- message: "The slug has to follow the pattern [0-9a-z] followed by multiple optional groups of -[0-9a-z]. e.g. my-slug"
378
- }).max(MAX_SLUG_LENGTH, {
379
- message: `slug can be max ${MAX_SLUG_LENGTH} characters long`
466
+ // packages/models/src/lib/plugin-config.ts
467
+ var pluginMetaSchema = packageVersionSchema().merge(
468
+ metaSchema({
469
+ titleDescription: "Descriptive name",
470
+ descriptionDescription: "Description (markdown)",
471
+ docsUrlDescription: "Plugin documentation site",
472
+ description: "Plugin metadata"
473
+ })
474
+ ).merge(
475
+ z11.object({
476
+ slug: slugSchema.describe("Unique plugin slug within core config"),
477
+ icon: materialIconSchema
478
+ })
479
+ );
480
+ var pluginDataSchema = z11.object({
481
+ runner: z11.union([runnerConfigSchema, runnerFunctionSchema]),
482
+ audits: pluginAuditsSchema,
483
+ groups: groupsSchema
380
484
  });
381
- var descriptionSchema = z.string({ description: "Description (markdown)" }).max(MAX_DESCRIPTION_LENGTH).optional();
382
- var urlSchema = z.string().url();
383
- var docsUrlSchema = urlSchema.optional().or(z.literal("")).describe("Documentation site");
384
- var titleSchema = z.string({ description: "Descriptive name" }).max(MAX_TITLE_LENGTH);
385
- var scoreSchema = z.number({
386
- description: "Value between 0 and 1"
387
- }).min(0).max(1);
388
- function metaSchema(options) {
389
- const {
390
- descriptionDescription,
391
- titleDescription,
392
- docsUrlDescription,
393
- description
394
- } = options ?? {};
395
- return z.object(
396
- {
397
- title: titleDescription ? titleSchema.describe(titleDescription) : titleSchema,
398
- description: descriptionDescription ? descriptionSchema.describe(descriptionDescription) : descriptionSchema,
399
- docsUrl: docsUrlDescription ? docsUrlSchema.describe(docsUrlDescription) : docsUrlSchema
400
- },
401
- { description }
402
- );
403
- }
404
- var filePathSchema = z.string().trim().min(1, { message: "path is invalid" });
405
- var fileNameSchema = z.string().trim().regex(filenameRegex, {
406
- message: `The filename has to be valid`
407
- }).min(1, { message: "file name is invalid" });
408
- var positiveIntSchema = z.number().int().positive();
409
- var nonnegativeIntSchema = z.number().int().nonnegative();
410
- function packageVersionSchema(options) {
411
- const { versionDescription = "NPM version of the package", required } = options ?? {};
412
- const packageSchema = z.string({ description: "NPM package name" });
413
- const versionSchema = z.string({ description: versionDescription });
414
- return z.object(
415
- {
416
- packageName: required ? packageSchema : packageSchema.optional(),
417
- version: required ? versionSchema : versionSchema.optional()
418
- },
419
- { description: "NPM package name and version of a published package" }
420
- );
421
- }
422
- var weightSchema = nonnegativeIntSchema.describe(
423
- "Coefficient for the given score (use weight 0 if only for display)"
485
+ var pluginConfigSchema = pluginMetaSchema.merge(pluginDataSchema).refine(
486
+ (pluginCfg) => !getMissingRefsFromGroups(pluginCfg),
487
+ (pluginCfg) => ({
488
+ message: missingRefsFromGroupsErrorMsg(pluginCfg)
489
+ })
424
490
  );
425
- function weightedRefSchema(description, slugDescription) {
426
- return z.object(
427
- {
428
- slug: slugSchema.describe(slugDescription),
429
- weight: weightSchema.describe("Weight used to calculate score")
430
- },
431
- { description }
432
- );
491
+ function missingRefsFromGroupsErrorMsg(pluginCfg) {
492
+ const missingRefs = getMissingRefsFromGroups(pluginCfg);
493
+ return `The following group references need to point to an existing audit in this plugin config: ${errorItems(
494
+ missingRefs
495
+ )}`;
433
496
  }
434
- function scorableSchema(description, refSchema, duplicateCheckFn, duplicateMessageFn) {
435
- return z.object(
436
- {
437
- slug: slugSchema.describe('Human-readable unique ID, e.g. "performance"'),
438
- refs: z.array(refSchema).min(1).refine(
439
- (refs) => !duplicateCheckFn(refs),
440
- (refs) => ({
441
- message: duplicateMessageFn(refs)
442
- })
443
- ).refine(hasNonZeroWeightedRef, () => ({
444
- message: "In a category there has to be at least one ref with weight > 0"
445
- }))
446
- },
447
- { description }
497
+ function getMissingRefsFromGroups(pluginCfg) {
498
+ return hasMissingStrings(
499
+ pluginCfg.groups?.flatMap(
500
+ ({ refs: audits }) => audits.map(({ slug: ref }) => ref)
501
+ ) ?? [],
502
+ pluginCfg.audits.map(({ slug }) => slug)
448
503
  );
449
504
  }
450
- var materialIconSchema = z.enum(MATERIAL_ICONS, {
451
- description: "Icon from VSCode Material Icons extension"
505
+
506
+ // packages/models/src/lib/upload-config.ts
507
+ import { z as z12 } from "zod";
508
+ var uploadConfigSchema = z12.object({
509
+ server: urlSchema.describe("URL of deployed portal API"),
510
+ apiKey: z12.string({
511
+ description: "API key with write access to portal (use `process.env` for security)"
512
+ }),
513
+ organization: slugSchema.describe(
514
+ "Organization slug from Code PushUp portal"
515
+ ),
516
+ project: slugSchema.describe("Project slug from Code PushUp portal"),
517
+ timeout: z12.number({ description: "Request timeout in minutes (default is 5)" }).positive().int().optional()
518
+ });
519
+
520
+ // packages/models/src/lib/core-config.ts
521
+ var unrefinedCoreConfigSchema = z13.object({
522
+ plugins: z13.array(pluginConfigSchema, {
523
+ description: "List of plugins to be used (official, community-provided, or custom)"
524
+ }).min(1),
525
+ /** portal configuration for persisting results */
526
+ persist: persistConfigSchema.optional(),
527
+ /** portal configuration for uploading results */
528
+ upload: uploadConfigSchema.optional(),
529
+ categories: categoriesSchema.optional()
452
530
  });
453
- function hasNonZeroWeightedRef(refs) {
454
- return refs.reduce((acc, { weight }) => weight + acc, 0) !== 0;
531
+ var coreConfigSchema = refineCoreConfig(unrefinedCoreConfigSchema);
532
+ function refineCoreConfig(schema) {
533
+ return schema.refine(
534
+ (coreCfg) => !getMissingRefsForCategories(coreCfg.categories ?? [], coreCfg.plugins),
535
+ (coreCfg) => ({
536
+ message: missingRefsForCategoriesErrorMsg(
537
+ coreCfg.categories ?? [],
538
+ coreCfg.plugins
539
+ )
540
+ })
541
+ );
455
542
  }
456
543
 
457
- // packages/models/src/lib/audit.ts
458
- import { z as z2 } from "zod";
459
- var auditSchema = z2.object({
460
- slug: slugSchema.describe("ID (unique within plugin)")
461
- }).merge(
462
- metaSchema({
463
- titleDescription: "Descriptive name",
464
- descriptionDescription: "Description (markdown)",
465
- docsUrlDescription: "Link to documentation (rationale)",
466
- description: "List of scorable metrics for the given plugin"
544
+ // packages/models/src/lib/report.ts
545
+ import { z as z14 } from "zod";
546
+ var auditReportSchema = auditSchema.merge(auditOutputSchema);
547
+ var pluginReportSchema = pluginMetaSchema.merge(
548
+ executionMetaSchema({
549
+ descriptionDate: "Start date and time of plugin run",
550
+ descriptionDuration: "Duration of the plugin run in ms"
467
551
  })
468
- );
469
- var pluginAuditsSchema = z2.array(auditSchema, {
470
- description: "List of audits maintained in a plugin"
471
- }).min(1).refine(
472
- (auditMetadata) => !getDuplicateSlugsInAudits(auditMetadata),
473
- (auditMetadata) => ({
474
- message: duplicateSlugsInAuditsErrorMsg(auditMetadata)
552
+ ).merge(
553
+ z14.object({
554
+ audits: z14.array(auditReportSchema).min(1),
555
+ groups: z14.array(groupSchema).optional()
556
+ })
557
+ ).refine(
558
+ (pluginReport) => !getMissingRefsFromGroups2(pluginReport.audits, pluginReport.groups ?? []),
559
+ (pluginReport) => ({
560
+ message: missingRefsFromGroupsErrorMsg2(
561
+ pluginReport.audits,
562
+ pluginReport.groups ?? []
563
+ )
475
564
  })
476
565
  );
477
- function duplicateSlugsInAuditsErrorMsg(audits) {
478
- const duplicateRefs = getDuplicateSlugsInAudits(audits);
479
- return `In plugin audits the following slugs are not unique: ${errorItems(
480
- duplicateRefs
566
+ function missingRefsFromGroupsErrorMsg2(audits, groups) {
567
+ const missingRefs = getMissingRefsFromGroups2(audits, groups);
568
+ return `group references need to point to an existing audit in this plugin report: ${errorItems(
569
+ missingRefs
481
570
  )}`;
482
571
  }
483
- function getDuplicateSlugsInAudits(audits) {
484
- return hasDuplicateStrings(audits.map(({ slug }) => slug));
572
+ function getMissingRefsFromGroups2(audits, groups) {
573
+ return hasMissingStrings(
574
+ groups.flatMap(
575
+ ({ refs: auditRefs }) => auditRefs.map(({ slug: ref }) => ref)
576
+ ),
577
+ audits.map(({ slug }) => slug)
578
+ );
485
579
  }
486
-
487
- // packages/models/src/lib/audit-output.ts
488
- import { z as z5 } from "zod";
489
-
490
- // packages/models/src/lib/issue.ts
491
- import { z as z3 } from "zod";
492
- var sourceFileLocationSchema = z3.object(
493
- {
494
- file: filePathSchema.describe("Relative path to source file in Git repo"),
495
- position: z3.object(
496
- {
497
- startLine: positiveIntSchema.describe("Start line"),
498
- startColumn: positiveIntSchema.describe("Start column").optional(),
499
- endLine: positiveIntSchema.describe("End line").optional(),
500
- endColumn: positiveIntSchema.describe("End column").optional()
501
- },
502
- { description: "Location in file" }
503
- ).optional()
504
- },
505
- { description: "Source file location" }
506
- );
507
- var issueSeveritySchema = z3.enum(["info", "warning", "error"], {
508
- description: "Severity level"
509
- });
510
- var issueSchema = z3.object(
511
- {
512
- message: z3.string({ description: "Descriptive error message" }).max(MAX_ISSUE_MESSAGE_LENGTH),
513
- severity: issueSeveritySchema,
514
- source: sourceFileLocationSchema.optional()
515
- },
516
- { description: "Issue information" }
517
- );
518
-
519
- // packages/models/src/lib/table.ts
520
- import { z as z4 } from "zod";
521
- var tableAlignmentSchema = z4.enum(["left", "center", "right"], {
522
- description: "Cell alignment"
523
- });
524
- var tableColumnObjectSchema = z4.object({
525
- key: z4.string(),
526
- label: z4.string().optional(),
527
- align: tableAlignmentSchema.optional()
528
- });
529
- var tableRowObjectSchema = z4.record(primitiveValueSchema, {
530
- description: "Object row"
531
- });
532
- var tableRowPrimitiveSchema = z4.array(primitiveValueSchema, {
533
- description: "Primitive row"
534
- });
535
- var tableSharedSchema = z4.object({
536
- title: z4.string().optional().describe("Display title for table")
537
- });
538
- var tablePrimitiveSchema = tableSharedSchema.merge(
539
- z4.object(
580
+ var reportSchema = packageVersionSchema({
581
+ versionDescription: "NPM version of the CLI",
582
+ required: true
583
+ }).merge(
584
+ executionMetaSchema({
585
+ descriptionDate: "Start date and time of the collect run",
586
+ descriptionDuration: "Duration of the collect run in ms"
587
+ })
588
+ ).merge(
589
+ z14.object(
540
590
  {
541
- columns: z4.array(tableAlignmentSchema).optional(),
542
- rows: z4.array(tableRowPrimitiveSchema)
591
+ categories: z14.array(categoryConfigSchema),
592
+ plugins: z14.array(pluginReportSchema).min(1),
593
+ commit: commitSchema.describe("Git commit for which report was collected").nullable()
543
594
  },
544
- { description: "Table with primitive rows and optional alignment columns" }
595
+ { description: "Collect output data" }
545
596
  )
597
+ ).refine(
598
+ (report) => !getMissingRefsForCategories(report.categories, report.plugins),
599
+ (report) => ({
600
+ message: missingRefsForCategoriesErrorMsg(
601
+ report.categories,
602
+ report.plugins
603
+ )
604
+ })
546
605
  );
547
- var tableObjectSchema = tableSharedSchema.merge(
548
- z4.object(
606
+
607
+ // packages/models/src/lib/reports-diff.ts
608
+ import { z as z15 } from "zod";
609
+ function makeComparisonSchema(schema) {
610
+ const sharedDescription = schema.description || "Result";
611
+ return z15.object({
612
+ before: schema.describe(`${sharedDescription} (source commit)`),
613
+ after: schema.describe(`${sharedDescription} (target commit)`)
614
+ });
615
+ }
616
+ function makeArraysComparisonSchema(diffSchema, resultSchema, description) {
617
+ return z15.object(
549
618
  {
550
- columns: z4.union([
551
- z4.array(tableAlignmentSchema),
552
- z4.array(tableColumnObjectSchema)
553
- ]).optional(),
554
- rows: z4.array(tableRowObjectSchema)
619
+ changed: z15.array(diffSchema),
620
+ unchanged: z15.array(resultSchema),
621
+ added: z15.array(resultSchema),
622
+ removed: z15.array(resultSchema)
555
623
  },
556
- {
557
- description: "Table with object rows and optional alignment or object columns"
558
- }
559
- )
560
- );
561
- var tableSchema = (description = "Table information") => z4.union([tablePrimitiveSchema, tableObjectSchema], { description });
562
-
563
- // packages/models/src/lib/audit-output.ts
564
- var auditValueSchema = nonnegativeIntSchema.describe("Raw numeric value");
565
- var auditDisplayValueSchema = z5.string({ description: "Formatted value (e.g. '0.9 s', '2.1 MB')" }).optional();
566
- var auditDetailsSchema = z5.object(
567
- {
568
- issues: z5.array(issueSchema, { description: "List of findings" }).optional(),
569
- table: tableSchema("Table of related findings").optional()
570
- },
571
- { description: "Detailed information" }
624
+ { description }
625
+ );
626
+ }
627
+ var scorableMetaSchema = z15.object({
628
+ slug: slugSchema,
629
+ title: titleSchema,
630
+ docsUrl: docsUrlSchema
631
+ });
632
+ var scorableWithPluginMetaSchema = scorableMetaSchema.merge(
633
+ z15.object({
634
+ plugin: pluginMetaSchema.pick({ slug: true, title: true, docsUrl: true }).describe("Plugin which defines it")
635
+ })
572
636
  );
573
- var auditOutputSchema = z5.object(
574
- {
575
- slug: slugSchema.describe("Reference to audit"),
576
- displayValue: auditDisplayValueSchema,
577
- value: auditValueSchema,
578
- score: scoreSchema,
579
- details: auditDetailsSchema.optional()
580
- },
581
- { description: "Audit information" }
637
+ var scorableDiffSchema = scorableMetaSchema.merge(
638
+ z15.object({
639
+ scores: makeComparisonSchema(scoreSchema).merge(
640
+ z15.object({
641
+ diff: z15.number().min(-1).max(1).describe("Score change (`scores.after - scores.before`)")
642
+ })
643
+ ).describe("Score comparison")
644
+ })
582
645
  );
583
- var auditOutputsSchema = z5.array(auditOutputSchema, {
584
- description: "List of JSON formatted audit output emitted by the runner process of a plugin"
585
- }).refine(
586
- (audits) => !getDuplicateSlugsInAudits2(audits),
587
- (audits) => ({ message: duplicateSlugsInAuditsErrorMsg2(audits) })
646
+ var scorableWithPluginDiffSchema = scorableDiffSchema.merge(
647
+ scorableWithPluginMetaSchema
588
648
  );
589
- function duplicateSlugsInAuditsErrorMsg2(audits) {
590
- const duplicateRefs = getDuplicateSlugsInAudits2(audits);
591
- return `In plugin audits the slugs are not unique: ${errorItems(
592
- duplicateRefs
593
- )}`;
594
- }
595
- function getDuplicateSlugsInAudits2(audits) {
596
- return hasDuplicateStrings(audits.map(({ slug }) => slug));
597
- }
598
-
599
- // packages/models/src/lib/category-config.ts
600
- import { z as z6 } from "zod";
601
- var categoryRefSchema = weightedRefSchema(
602
- "Weighted references to audits and/or groups for the category",
603
- "Slug of an audit or group (depending on `type`)"
604
- ).merge(
605
- z6.object({
606
- type: z6.enum(["audit", "group"], {
607
- description: "Discriminant for reference kind, affects where `slug` is looked up"
608
- }),
609
- plugin: slugSchema.describe(
610
- "Plugin slug (plugin should contain referenced audit or group)"
649
+ var categoryDiffSchema = scorableDiffSchema;
650
+ var groupDiffSchema = scorableWithPluginDiffSchema;
651
+ var auditDiffSchema = scorableWithPluginDiffSchema.merge(
652
+ z15.object({
653
+ values: makeComparisonSchema(auditValueSchema).merge(
654
+ z15.object({
655
+ diff: z15.number().int().describe("Value change (`values.after - values.before`)")
656
+ })
657
+ ).describe("Audit `value` comparison"),
658
+ displayValues: makeComparisonSchema(auditDisplayValueSchema).describe(
659
+ "Audit `displayValue` comparison"
611
660
  )
612
661
  })
613
662
  );
614
- var categoryConfigSchema = scorableSchema(
615
- "Category with a score calculated from audits and groups from various plugins",
616
- categoryRefSchema,
617
- getDuplicateRefsInCategoryMetrics,
618
- duplicateRefsInCategoryMetricsErrorMsg
619
- ).merge(
620
- metaSchema({
621
- titleDescription: "Category Title",
622
- docsUrlDescription: "Category docs URL",
623
- descriptionDescription: "Category description",
624
- description: "Meta info for category"
663
+ var categoryResultSchema = scorableMetaSchema.merge(
664
+ z15.object({ score: scoreSchema })
665
+ );
666
+ var groupResultSchema = scorableWithPluginMetaSchema.merge(
667
+ z15.object({ score: scoreSchema })
668
+ );
669
+ var auditResultSchema = scorableWithPluginMetaSchema.merge(
670
+ auditOutputSchema.pick({ score: true, value: true, displayValue: true })
671
+ );
672
+ var reportsDiffSchema = z15.object({
673
+ commits: makeComparisonSchema(commitSchema).nullable().describe("Commits identifying compared reports"),
674
+ categories: makeArraysComparisonSchema(
675
+ categoryDiffSchema,
676
+ categoryResultSchema,
677
+ "Changes affecting categories"
678
+ ),
679
+ groups: makeArraysComparisonSchema(
680
+ groupDiffSchema,
681
+ groupResultSchema,
682
+ "Changes affecting groups"
683
+ ),
684
+ audits: makeArraysComparisonSchema(
685
+ auditDiffSchema,
686
+ auditResultSchema,
687
+ "Changes affecting audits"
688
+ )
689
+ }).merge(
690
+ packageVersionSchema({
691
+ versionDescription: "NPM version of the CLI (when `compare` was run)",
692
+ required: true
625
693
  })
626
694
  ).merge(
627
- z6.object({
628
- isBinary: z6.boolean({
629
- description: 'Is this a binary category (i.e. only a perfect score considered a "pass")?'
630
- }).optional()
695
+ executionMetaSchema({
696
+ descriptionDate: "Start date and time of the compare run",
697
+ descriptionDuration: "Duration of the compare run in ms"
631
698
  })
632
699
  );
633
- function duplicateRefsInCategoryMetricsErrorMsg(metrics) {
634
- const duplicateRefs = getDuplicateRefsInCategoryMetrics(metrics);
635
- return `In the categories, the following audit or group refs are duplicates: ${errorItems(
636
- duplicateRefs
637
- )}`;
700
+
701
+ // packages/utils/src/lib/execute-process.ts
702
+ import { spawn } from "node:child_process";
703
+
704
+ // packages/utils/src/lib/file-system.ts
705
+ import { bundleRequire } from "bundle-require";
706
+ import chalk2 from "chalk";
707
+ import { mkdir, readFile, readdir, rm, stat } from "node:fs/promises";
708
+ import { join } from "node:path";
709
+
710
+ // packages/utils/src/lib/logging.ts
711
+ import isaacs_cliui from "@isaacs/cliui";
712
+ import { cliui } from "@poppinss/cliui";
713
+ import chalk from "chalk";
714
+
715
+ // packages/utils/src/lib/reports/constants.ts
716
+ var TERMINAL_WIDTH = 80;
717
+
718
+ // packages/utils/src/lib/logging.ts
719
+ var singletonUiInstance;
720
+ function ui() {
721
+ if (singletonUiInstance === void 0) {
722
+ singletonUiInstance = cliui();
723
+ }
724
+ return {
725
+ ...singletonUiInstance,
726
+ row: (args) => {
727
+ logListItem(args);
728
+ }
729
+ };
638
730
  }
639
- function getDuplicateRefsInCategoryMetrics(metrics) {
640
- return hasDuplicateStrings(
641
- metrics.map(({ slug, type, plugin }) => `${type} :: ${plugin} / ${slug}`)
642
- );
731
+ var singletonisaacUi;
732
+ function logListItem(args) {
733
+ if (singletonisaacUi === void 0) {
734
+ singletonisaacUi = isaacs_cliui({ width: TERMINAL_WIDTH });
735
+ }
736
+ singletonisaacUi.div(...args);
737
+ const content = singletonisaacUi.toString();
738
+ singletonisaacUi.rows = [];
739
+ singletonUiInstance?.logger.log(content);
643
740
  }
644
- var categoriesSchema = z6.array(categoryConfigSchema, {
645
- description: "Categorization of individual audits"
646
- }).refine(
647
- (categoryCfg) => !getDuplicateSlugCategories(categoryCfg),
648
- (categoryCfg) => ({
649
- message: duplicateSlugCategoriesErrorMsg(categoryCfg)
650
- })
651
- );
652
- function duplicateSlugCategoriesErrorMsg(categories) {
653
- const duplicateStringSlugs = getDuplicateSlugCategories(categories);
654
- return `In the categories, the following slugs are duplicated: ${errorItems(
655
- duplicateStringSlugs
656
- )}`;
741
+
742
+ // packages/utils/src/lib/file-system.ts
743
+ async function readTextFile(path) {
744
+ const buffer = await readFile(path);
745
+ return buffer.toString();
657
746
  }
658
- function getDuplicateSlugCategories(categories) {
659
- return hasDuplicateStrings(categories.map(({ slug }) => slug));
747
+ async function readJsonFile(path) {
748
+ const text = await readTextFile(path);
749
+ return JSON.parse(text);
750
+ }
751
+ async function ensureDirectoryExists(baseDir) {
752
+ try {
753
+ await mkdir(baseDir, { recursive: true });
754
+ return;
755
+ } catch (error) {
756
+ ui().logger.info(error.message);
757
+ if (error.code !== "EEXIST") {
758
+ throw error;
759
+ }
760
+ }
761
+ }
762
+ function pluginWorkDir(slug) {
763
+ return join("node_modules", ".code-pushup", slug);
660
764
  }
661
765
 
662
- // packages/models/src/lib/commit.ts
663
- import { z as z7 } from "zod";
664
- var commitSchema = z7.object(
665
- {
666
- hash: z7.string({ description: "Commit SHA (full)" }).regex(
667
- /^[\da-f]{40}$/,
668
- "Commit SHA should be a 40-character hexadecimal string"
669
- ),
670
- message: z7.string({ description: "Commit message" }),
671
- date: z7.coerce.date({
672
- description: "Date and time when commit was authored"
673
- }),
674
- author: z7.string({
675
- description: "Commit author name"
676
- }).trim()
677
- },
678
- { description: "Git commit" }
679
- );
766
+ // packages/utils/src/lib/text-formats/constants.ts
767
+ var NEW_LINE = "\n";
768
+ var TAB = " ";
680
769
 
681
- // packages/models/src/lib/core-config.ts
682
- import { z as z13 } from "zod";
770
+ // packages/utils/src/lib/text-formats/html/details.ts
771
+ function details(title, content, cfg = { open: false }) {
772
+ return `<details${cfg.open ? " open" : ""}>${NEW_LINE}<summary>${title}</summary>${NEW_LINE}${// ⚠️ The blank line is needed to ensure Markdown in content is rendered correctly.
773
+ NEW_LINE}${content}${NEW_LINE}${// @TODO in the future we could consider adding it only if the content ends with a code block
774
+ // ⚠️ The blank line ensure Markdown in content is rendered correctly.
775
+ NEW_LINE}</details>${// ⚠️ The blank line is needed to ensure Markdown after details is rendered correctly.
776
+ NEW_LINE}`;
777
+ }
683
778
 
684
- // packages/models/src/lib/persist-config.ts
685
- import { z as z8 } from "zod";
686
- var formatSchema = z8.enum(["json", "md"]);
687
- var persistConfigSchema = z8.object({
688
- outputDir: filePathSchema.describe("Artifacts folder").optional(),
689
- filename: fileNameSchema.describe("Artifacts file name (without extension)").optional(),
690
- format: z8.array(formatSchema).optional()
691
- });
779
+ // packages/utils/src/lib/text-formats/html/font-style.ts
780
+ var boldElement = "b";
781
+ function bold(text) {
782
+ return `<${boldElement}>${text}</${boldElement}>`;
783
+ }
784
+ var italicElement = "i";
785
+ function italic(text) {
786
+ return `<${italicElement}>${text}</${italicElement}>`;
787
+ }
788
+ var codeElement = "code";
789
+ function code(text) {
790
+ return `<${codeElement}>${text}</${codeElement}>`;
791
+ }
692
792
 
693
- // packages/models/src/lib/plugin-config.ts
694
- import { z as z11 } from "zod";
793
+ // packages/utils/src/lib/text-formats/html/link.ts
794
+ function link(href, text) {
795
+ return `<a href="${href}">${text || href}"</a>`;
796
+ }
695
797
 
696
- // packages/models/src/lib/group.ts
697
- import { z as z9 } from "zod";
698
- var groupRefSchema = weightedRefSchema(
699
- "Weighted reference to a group",
700
- "Reference slug to a group within this plugin (e.g. 'max-lines')"
701
- );
702
- var groupMetaSchema = metaSchema({
703
- titleDescription: "Descriptive name for the group",
704
- descriptionDescription: "Description of the group (markdown)",
705
- docsUrlDescription: "Group documentation site",
706
- description: "Group metadata"
707
- });
708
- var groupSchema = scorableSchema(
709
- 'A group aggregates a set of audits into a single score which can be referenced from a category. E.g. the group slug "performance" groups audits and can be referenced in a category',
710
- groupRefSchema,
711
- getDuplicateRefsInGroups,
712
- duplicateRefsInGroupsErrorMsg
713
- ).merge(groupMetaSchema);
714
- var groupsSchema = z9.array(groupSchema, {
715
- description: "List of groups"
716
- }).optional().refine(
717
- (groups) => !getDuplicateSlugsInGroups(groups),
718
- (groups) => ({
719
- message: duplicateSlugsInGroupsErrorMsg(groups)
720
- })
721
- );
722
- function duplicateRefsInGroupsErrorMsg(groups) {
723
- const duplicateRefs = getDuplicateRefsInGroups(groups);
724
- return `In plugin groups the following references are not unique: ${errorItems(
725
- duplicateRefs
798
+ // packages/utils/src/lib/transform.ts
799
+ import { platform } from "node:os";
800
+ function toUnixNewlines(text) {
801
+ return platform() === "win32" ? text.replace(/\r\n/g, "\n") : text;
802
+ }
803
+ function capitalize(text) {
804
+ return `${text.charAt(0).toLocaleUpperCase()}${text.slice(
805
+ 1
726
806
  )}`;
727
807
  }
728
- function getDuplicateRefsInGroups(groups) {
729
- return hasDuplicateStrings(groups.map(({ slug: ref }) => ref).filter(exists));
808
+ function toNumberPrecision(value, decimalPlaces) {
809
+ return Number(
810
+ `${Math.round(
811
+ Number.parseFloat(`${value}e${decimalPlaces}`)
812
+ )}e-${decimalPlaces}`
813
+ );
814
+ }
815
+ function toOrdinal(value) {
816
+ if (value % 10 === 1 && value % 100 !== 11) {
817
+ return `${value}st`;
818
+ }
819
+ if (value % 10 === 2 && value % 100 !== 12) {
820
+ return `${value}nd`;
821
+ }
822
+ if (value % 10 === 3 && value % 100 !== 13) {
823
+ return `${value}rd`;
824
+ }
825
+ return `${value}th`;
826
+ }
827
+
828
+ // packages/utils/src/lib/table.ts
829
+ function rowToStringArray({ rows, columns = [] }) {
830
+ if (Array.isArray(rows.at(0)) && typeof columns.at(0) === "object") {
831
+ throw new TypeError(
832
+ "Column can`t be object when rows are primitive values"
833
+ );
834
+ }
835
+ return rows.map((row) => {
836
+ if (Array.isArray(row)) {
837
+ return row.map(String);
838
+ }
839
+ const objectRow = row;
840
+ if (columns.length === 0 || typeof columns.at(0) === "string") {
841
+ return Object.values(objectRow).map(String);
842
+ }
843
+ return columns.map(
844
+ ({ key }) => String(objectRow[key])
845
+ );
846
+ });
730
847
  }
731
- function duplicateSlugsInGroupsErrorMsg(groups) {
732
- const duplicateRefs = getDuplicateSlugsInGroups(groups);
733
- return `In groups the following slugs are not unique: ${errorItems(
734
- duplicateRefs
735
- )}`;
848
+ function columnsToStringArray({ rows, columns = [] }) {
849
+ const firstRow = rows.at(0);
850
+ const primitiveRows = Array.isArray(firstRow);
851
+ if (typeof columns.at(0) === "string" && !primitiveRows) {
852
+ throw new Error("invalid union type. Caught by model parsing.");
853
+ }
854
+ if (columns.length === 0) {
855
+ if (Array.isArray(firstRow)) {
856
+ return firstRow.map((_, idx) => String(idx));
857
+ }
858
+ return Object.keys(firstRow);
859
+ }
860
+ if (typeof columns.at(0) === "string") {
861
+ return columns.map(String);
862
+ }
863
+ const cols = columns;
864
+ return cols.map(({ label, key }) => label ?? capitalize(key));
736
865
  }
737
- function getDuplicateSlugsInGroups(groups) {
738
- return Array.isArray(groups) ? hasDuplicateStrings(groups.map(({ slug }) => slug)) : false;
866
+ function getColumnAlignmentForKeyAndIndex(targetKey, targetIdx, columns = []) {
867
+ const column = columns.at(targetIdx) ?? columns.find((col) => col.key === targetKey);
868
+ if (typeof column === "string") {
869
+ return column;
870
+ } else if (typeof column === "object") {
871
+ return column.align ?? "center";
872
+ } else {
873
+ return "center";
874
+ }
739
875
  }
740
-
741
- // packages/models/src/lib/runner-config.ts
742
- import { z as z10 } from "zod";
743
- var outputTransformSchema = z10.function().args(z10.unknown()).returns(z10.union([auditOutputsSchema, z10.promise(auditOutputsSchema)]));
744
- var runnerConfigSchema = z10.object(
745
- {
746
- command: z10.string({
747
- description: "Shell command to execute"
748
- }),
749
- args: z10.array(z10.string({ description: "Command arguments" })).optional(),
750
- outputFile: filePathSchema.describe("Output path"),
751
- outputTransform: outputTransformSchema.optional()
752
- },
753
- {
754
- description: "How to execute runner"
876
+ function getColumnAlignmentForIndex(targetIdx, columns = []) {
877
+ const column = columns.at(targetIdx);
878
+ if (column == null) {
879
+ return "center";
880
+ } else if (typeof column === "string") {
881
+ return column;
882
+ } else if (typeof column === "object") {
883
+ return column.align ?? "center";
884
+ } else {
885
+ return "center";
755
886
  }
756
- );
757
- var onProgressSchema = z10.function().args(z10.unknown()).returns(z10.void());
758
- var runnerFunctionSchema = z10.function().args(onProgressSchema.optional()).returns(z10.union([auditOutputsSchema, z10.promise(auditOutputsSchema)]));
759
-
760
- // packages/models/src/lib/plugin-config.ts
761
- var pluginMetaSchema = packageVersionSchema().merge(
762
- metaSchema({
763
- titleDescription: "Descriptive name",
764
- descriptionDescription: "Description (markdown)",
765
- docsUrlDescription: "Plugin documentation site",
766
- description: "Plugin metadata"
767
- })
768
- ).merge(
769
- z11.object({
770
- slug: slugSchema.describe("Unique plugin slug within core config"),
771
- icon: materialIconSchema
772
- })
773
- );
774
- var pluginDataSchema = z11.object({
775
- runner: z11.union([runnerConfigSchema, runnerFunctionSchema]),
776
- audits: pluginAuditsSchema,
777
- groups: groupsSchema
778
- });
779
- var pluginConfigSchema = pluginMetaSchema.merge(pluginDataSchema).refine(
780
- (pluginCfg) => !getMissingRefsFromGroups(pluginCfg),
781
- (pluginCfg) => ({
782
- message: missingRefsFromGroupsErrorMsg(pluginCfg)
783
- })
784
- );
785
- function missingRefsFromGroupsErrorMsg(pluginCfg) {
786
- const missingRefs = getMissingRefsFromGroups(pluginCfg);
787
- return `The following group references need to point to an existing audit in this plugin config: ${errorItems(
788
- missingRefs
789
- )}`;
790
887
  }
791
- function getMissingRefsFromGroups(pluginCfg) {
792
- return hasMissingStrings(
793
- pluginCfg.groups?.flatMap(
794
- ({ refs: audits }) => audits.map(({ slug: ref }) => ref)
795
- ) ?? [],
796
- pluginCfg.audits.map(({ slug }) => slug)
888
+ function getColumnAlignments({
889
+ rows,
890
+ columns = []
891
+ }) {
892
+ if (rows.at(0) == null) {
893
+ throw new Error("first row can`t be undefined.");
894
+ }
895
+ if (Array.isArray(rows.at(0))) {
896
+ const firstPrimitiveRow = rows.at(0);
897
+ return Array.from({ length: firstPrimitiveRow.length }).map(
898
+ (_, idx) => getColumnAlignmentForIndex(idx, columns)
899
+ );
900
+ }
901
+ const firstObject = rows.at(0);
902
+ return Object.keys(firstObject).map(
903
+ (key, idx) => getColumnAlignmentForKeyAndIndex(key, idx, columns)
797
904
  );
798
905
  }
799
906
 
800
- // packages/models/src/lib/upload-config.ts
801
- import { z as z12 } from "zod";
802
- var uploadConfigSchema = z12.object({
803
- server: urlSchema.describe("URL of deployed portal API"),
804
- apiKey: z12.string({
805
- description: "API key with write access to portal (use `process.env` for security)"
806
- }),
807
- organization: slugSchema.describe(
808
- "Organization slug from Code PushUp portal"
809
- ),
810
- project: slugSchema.describe("Project slug from Code PushUp portal"),
811
- timeout: z12.number({ description: "Request timeout in minutes (default is 5)" }).positive().int().optional()
812
- });
813
-
814
- // packages/models/src/lib/core-config.ts
815
- var unrefinedCoreConfigSchema = z13.object({
816
- plugins: z13.array(pluginConfigSchema, {
817
- description: "List of plugins to be used (official, community-provided, or custom)"
818
- }).min(1),
819
- /** portal configuration for persisting results */
820
- persist: persistConfigSchema.optional(),
821
- /** portal configuration for uploading results */
822
- upload: uploadConfigSchema.optional(),
823
- categories: categoriesSchema.optional()
824
- });
825
- var coreConfigSchema = refineCoreConfig(unrefinedCoreConfigSchema);
826
- function refineCoreConfig(schema) {
827
- return schema.refine(
828
- (coreCfg) => !getMissingRefsForCategories(coreCfg.categories ?? [], coreCfg.plugins),
829
- (coreCfg) => ({
830
- message: missingRefsForCategoriesErrorMsg(
831
- coreCfg.categories ?? [],
832
- coreCfg.plugins
833
- )
834
- })
835
- );
907
+ // packages/utils/src/lib/text-formats/html/table.ts
908
+ function wrap(elem, content) {
909
+ return `<${elem}>${content}</${elem}>${NEW_LINE}`;
910
+ }
911
+ function wrapRow(content) {
912
+ const elem = "tr";
913
+ return `<${elem}>${NEW_LINE}${content}</${elem}>${NEW_LINE}`;
914
+ }
915
+ function table(tableData) {
916
+ if (tableData.rows.length === 0) {
917
+ throw new Error("Data can't be empty");
918
+ }
919
+ const tableHeaderCols = columnsToStringArray(tableData).map((s) => wrap("th", s)).join("");
920
+ const tableHeaderRow = wrapRow(tableHeaderCols);
921
+ const tableBody = rowToStringArray(tableData).map((arr) => {
922
+ const columns = arr.map((s) => wrap("td", s)).join("");
923
+ return wrapRow(columns);
924
+ }).join("");
925
+ return wrap("table", `${NEW_LINE}${tableHeaderRow}${tableBody}`);
836
926
  }
837
927
 
838
- // packages/models/src/lib/report.ts
839
- import { z as z14 } from "zod";
840
- var auditReportSchema = auditSchema.merge(auditOutputSchema);
841
- var pluginReportSchema = pluginMetaSchema.merge(
842
- executionMetaSchema({
843
- descriptionDate: "Start date and time of plugin run",
844
- descriptionDuration: "Duration of the plugin run in ms"
845
- })
846
- ).merge(
847
- z14.object({
848
- audits: z14.array(auditReportSchema).min(1),
849
- groups: z14.array(groupSchema).optional()
850
- })
851
- ).refine(
852
- (pluginReport) => !getMissingRefsFromGroups2(pluginReport.audits, pluginReport.groups ?? []),
853
- (pluginReport) => ({
854
- message: missingRefsFromGroupsErrorMsg2(
855
- pluginReport.audits,
856
- pluginReport.groups ?? []
857
- )
858
- })
859
- );
860
- function missingRefsFromGroupsErrorMsg2(audits, groups) {
861
- const missingRefs = getMissingRefsFromGroups2(audits, groups);
862
- return `group references need to point to an existing audit in this plugin report: ${errorItems(
863
- missingRefs
864
- )}`;
928
+ // packages/utils/src/lib/text-formats/md/font-style.ts
929
+ var boldWrap = "**";
930
+ function bold2(text) {
931
+ return `${boldWrap}${text}${boldWrap}`;
865
932
  }
866
- function getMissingRefsFromGroups2(audits, groups) {
867
- return hasMissingStrings(
868
- groups.flatMap(
869
- ({ refs: auditRefs }) => auditRefs.map(({ slug: ref }) => ref)
870
- ),
871
- audits.map(({ slug }) => slug)
872
- );
933
+ var italicWrap = "_";
934
+ function italic2(text) {
935
+ return `${italicWrap}${text}${italicWrap}`;
936
+ }
937
+ var strikeThroughWrap = "~";
938
+ function strikeThrough(text) {
939
+ return `${strikeThroughWrap}${text}${strikeThroughWrap}`;
940
+ }
941
+ var codeWrap = "`";
942
+ function code2(text) {
943
+ return `${codeWrap}${text}${codeWrap}`;
873
944
  }
874
- var reportSchema = packageVersionSchema({
875
- versionDescription: "NPM version of the CLI",
876
- required: true
877
- }).merge(
878
- executionMetaSchema({
879
- descriptionDate: "Start date and time of the collect run",
880
- descriptionDuration: "Duration of the collect run in ms"
881
- })
882
- ).merge(
883
- z14.object(
884
- {
885
- categories: z14.array(categoryConfigSchema),
886
- plugins: z14.array(pluginReportSchema).min(1),
887
- commit: commitSchema.describe("Git commit for which report was collected").nullable()
888
- },
889
- { description: "Collect output data" }
890
- )
891
- ).refine(
892
- (report) => !getMissingRefsForCategories(report.categories, report.plugins),
893
- (report) => ({
894
- message: missingRefsForCategoriesErrorMsg(
895
- report.categories,
896
- report.plugins
897
- )
898
- })
899
- );
900
945
 
901
- // packages/models/src/lib/reports-diff.ts
902
- import { z as z15 } from "zod";
903
- function makeComparisonSchema(schema) {
904
- const sharedDescription = schema.description || "Result";
905
- return z15.object({
906
- before: schema.describe(`${sharedDescription} (source commit)`),
907
- after: schema.describe(`${sharedDescription} (target commit)`)
908
- });
946
+ // packages/utils/src/lib/text-formats/md/headline.ts
947
+ function headline(text, hierarchy = 1) {
948
+ return `${"#".repeat(hierarchy)} ${text}${NEW_LINE}`;
909
949
  }
910
- function makeArraysComparisonSchema(diffSchema, resultSchema, description) {
911
- return z15.object(
912
- {
913
- changed: z15.array(diffSchema),
914
- unchanged: z15.array(resultSchema),
915
- added: z15.array(resultSchema),
916
- removed: z15.array(resultSchema)
917
- },
918
- { description }
919
- );
950
+ function h(text, hierarchy = 1) {
951
+ return headline(text, hierarchy);
952
+ }
953
+ function h1(text) {
954
+ return headline(text, 1);
955
+ }
956
+ function h2(text) {
957
+ return headline(text, 2);
958
+ }
959
+ function h3(text) {
960
+ return headline(text, 3);
961
+ }
962
+ function h4(text) {
963
+ return headline(text, 4);
964
+ }
965
+ function h5(text) {
966
+ return headline(text, 5);
967
+ }
968
+ function h6(text) {
969
+ return headline(text, 6);
920
970
  }
921
- var scorableMetaSchema = z15.object({
922
- slug: slugSchema,
923
- title: titleSchema,
924
- docsUrl: docsUrlSchema
925
- });
926
- var scorableWithPluginMetaSchema = scorableMetaSchema.merge(
927
- z15.object({
928
- plugin: pluginMetaSchema.pick({ slug: true, title: true, docsUrl: true }).describe("Plugin which defines it")
929
- })
930
- );
931
- var scorableDiffSchema = scorableMetaSchema.merge(
932
- z15.object({
933
- scores: makeComparisonSchema(scoreSchema).merge(
934
- z15.object({
935
- diff: z15.number().min(-1).max(1).describe("Score change (`scores.after - scores.before`)")
936
- })
937
- ).describe("Score comparison")
938
- })
939
- );
940
- var scorableWithPluginDiffSchema = scorableDiffSchema.merge(
941
- scorableWithPluginMetaSchema
942
- );
943
- var categoryDiffSchema = scorableDiffSchema;
944
- var groupDiffSchema = scorableWithPluginDiffSchema;
945
- var auditDiffSchema = scorableWithPluginDiffSchema.merge(
946
- z15.object({
947
- values: makeComparisonSchema(auditValueSchema).merge(
948
- z15.object({
949
- diff: z15.number().int().describe("Value change (`values.after - values.before`)")
950
- })
951
- ).describe("Audit `value` comparison"),
952
- displayValues: makeComparisonSchema(auditDisplayValueSchema).describe(
953
- "Audit `displayValue` comparison"
954
- )
955
- })
956
- );
957
- var categoryResultSchema = scorableMetaSchema.merge(
958
- z15.object({ score: scoreSchema })
959
- );
960
- var groupResultSchema = scorableWithPluginMetaSchema.merge(
961
- z15.object({ score: scoreSchema })
962
- );
963
- var auditResultSchema = scorableWithPluginMetaSchema.merge(
964
- auditOutputSchema.pick({ score: true, value: true, displayValue: true })
965
- );
966
- var reportsDiffSchema = z15.object({
967
- commits: makeComparisonSchema(commitSchema).nullable().describe("Commits identifying compared reports"),
968
- categories: makeArraysComparisonSchema(
969
- categoryDiffSchema,
970
- categoryResultSchema,
971
- "Changes affecting categories"
972
- ),
973
- groups: makeArraysComparisonSchema(
974
- groupDiffSchema,
975
- groupResultSchema,
976
- "Changes affecting groups"
977
- ),
978
- audits: makeArraysComparisonSchema(
979
- auditDiffSchema,
980
- auditResultSchema,
981
- "Changes affecting audits"
982
- )
983
- }).merge(
984
- packageVersionSchema({
985
- versionDescription: "NPM version of the CLI (when `compare` was run)",
986
- required: true
987
- })
988
- ).merge(
989
- executionMetaSchema({
990
- descriptionDate: "Start date and time of the compare run",
991
- descriptionDuration: "Duration of the compare run in ms"
992
- })
993
- );
994
971
 
995
- // packages/utils/src/lib/execute-process.ts
996
- import { spawn } from "node:child_process";
972
+ // packages/utils/src/lib/text-formats/md/image.ts
973
+ function image(src, alt) {
974
+ return `![${alt}](${src})`;
975
+ }
997
976
 
998
- // packages/utils/src/lib/file-system.ts
999
- import { bundleRequire } from "bundle-require";
1000
- import chalk2 from "chalk";
1001
- import { mkdir, readFile, readdir, rm, stat } from "node:fs/promises";
1002
- import { join } from "node:path";
977
+ // packages/utils/src/lib/text-formats/md/link.ts
978
+ function link2(href, text) {
979
+ return `[${text || href}](${href})`;
980
+ }
1003
981
 
1004
- // packages/utils/src/lib/logging.ts
1005
- import isaacs_cliui from "@isaacs/cliui";
1006
- import { cliui } from "@poppinss/cliui";
1007
- import chalk from "chalk";
982
+ // packages/utils/src/lib/text-formats/md/list.ts
983
+ function li(text, order = "unordered") {
984
+ const style = order === "unordered" ? "-" : "- [ ]";
985
+ return `${style} ${text}`;
986
+ }
987
+ function indentation(text, level = 1) {
988
+ return `${TAB.repeat(level)}${text}`;
989
+ }
1008
990
 
1009
- // packages/utils/src/lib/reports/constants.ts
1010
- var TERMINAL_WIDTH = 80;
991
+ // packages/utils/src/lib/text-formats/md/paragraphs.ts
992
+ function paragraphs(...sections) {
993
+ return sections.filter(Boolean).join(`${NEW_LINE}${NEW_LINE}`);
994
+ }
1011
995
 
1012
- // packages/utils/src/lib/logging.ts
1013
- var singletonUiInstance;
1014
- function ui() {
1015
- if (singletonUiInstance === void 0) {
1016
- singletonUiInstance = cliui();
1017
- }
1018
- return {
1019
- ...singletonUiInstance,
1020
- row: (args) => {
1021
- logListItem(args);
1022
- }
1023
- };
996
+ // packages/utils/src/lib/text-formats/md/section.ts
997
+ function section(...contents) {
998
+ return `${lines(...contents)}${NEW_LINE}`;
1024
999
  }
1025
- var singletonisaacUi;
1026
- function logListItem(args) {
1027
- if (singletonisaacUi === void 0) {
1028
- singletonisaacUi = isaacs_cliui({ width: TERMINAL_WIDTH });
1029
- }
1030
- singletonisaacUi.div(...args);
1031
- const content = singletonisaacUi.toString();
1032
- singletonisaacUi.rows = [];
1033
- singletonUiInstance?.logger.log(content);
1000
+ function lines(...contents) {
1001
+ return `${contents.filter(Boolean).join(NEW_LINE)}`;
1034
1002
  }
1035
1003
 
1036
- // packages/utils/src/lib/file-system.ts
1037
- async function readTextFile(path) {
1038
- const buffer = await readFile(path);
1039
- return buffer.toString();
1040
- }
1041
- async function readJsonFile(path) {
1042
- const text = await readTextFile(path);
1043
- return JSON.parse(text);
1004
+ // packages/utils/src/lib/text-formats/md/table.ts
1005
+ var alignString = /* @__PURE__ */ new Map([
1006
+ ["left", ":--"],
1007
+ ["center", ":--:"],
1008
+ ["right", "--:"]
1009
+ ]);
1010
+ function tableRow(rows) {
1011
+ return `|${rows.join("|")}|`;
1044
1012
  }
1045
- async function ensureDirectoryExists(baseDir) {
1046
- try {
1047
- await mkdir(baseDir, { recursive: true });
1048
- return;
1049
- } catch (error) {
1050
- ui().logger.info(error.message);
1051
- if (error.code !== "EEXIST") {
1052
- throw error;
1053
- }
1013
+ function table2(data) {
1014
+ if (data.rows.length === 0) {
1015
+ throw new Error("Data can't be empty");
1054
1016
  }
1017
+ const alignmentRow = getColumnAlignments(data).map(
1018
+ (s) => alignString.get(s) ?? String(alignString.get("center"))
1019
+ );
1020
+ return section(
1021
+ `${lines(
1022
+ tableRow(columnsToStringArray(data)),
1023
+ tableRow(alignmentRow),
1024
+ ...rowToStringArray(data).map(tableRow)
1025
+ )}`
1026
+ );
1055
1027
  }
1056
- function pluginWorkDir(slug) {
1057
- return join("node_modules", ".code-pushup", slug);
1058
- }
1028
+
1029
+ // packages/utils/src/lib/text-formats/index.ts
1030
+ var md = {
1031
+ bold: bold2,
1032
+ italic: italic2,
1033
+ strikeThrough,
1034
+ code: code2,
1035
+ link: link2,
1036
+ image,
1037
+ headline,
1038
+ h,
1039
+ h1,
1040
+ h2,
1041
+ h3,
1042
+ h4,
1043
+ h5,
1044
+ h6,
1045
+ indentation,
1046
+ lines,
1047
+ li,
1048
+ section,
1049
+ paragraphs,
1050
+ table: table2
1051
+ };
1052
+ var html = {
1053
+ bold,
1054
+ italic,
1055
+ code,
1056
+ link,
1057
+ details,
1058
+ table
1059
+ };
1059
1060
 
1060
1061
  // packages/utils/src/lib/reports/utils.ts
1061
1062
  var { image: image2, bold: boldMd } = md;
@@ -1425,6 +1426,11 @@ async function parseLcovFiles(results) {
1425
1426
  results.map(async (result) => {
1426
1427
  const resultsPath = typeof result === "string" ? result : result.resultsPath;
1427
1428
  const lcovFileContent = await readTextFile(resultsPath);
1429
+ if (lcovFileContent.trim() === "") {
1430
+ ui().logger.warning(
1431
+ `Coverage plugin: Empty lcov report file detected at ${resultsPath}.`
1432
+ );
1433
+ }
1428
1434
  const parsedRecords = parseLcov(toUnixNewlines(lcovFileContent));
1429
1435
  return parsedRecords.map((record) => ({
1430
1436
  ...record,